package com.hilon.segment.mybatis.service.impl;

import com.hilon.segment.mybatis.mapper.system.SysApiMapper;
import com.hilon.segment.mybatis.mapper.system.SysRoleMapper;
import com.hilon.segment.mybatis.mapper.system.SysUserMapper;
import com.hilon.segment.mybatis.pojo.dto.MyGrantedAuthorityDTO;
import com.hilon.segment.mybatis.pojo.po.system.SysApiDO;
import com.hilon.segment.mybatis.pojo.po.system.SysUserDO;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.io.Serial;
import java.io.Serializable;
import java.util.*;

@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Resource
    private SysUserMapper sysUserMapper;

    @Resource
    private SysRoleMapper roleMapper;

    @Resource
    private SysApiMapper apiMapper;

    /**
     * @param username 用户名
     * @return 用户信息
     * @throws UsernameNotFoundException 未找到用户时抛出.
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 按用户名查用户, 不要用带密码的项去匹配用户.
        SysUserDO sysUser = sysUserMapper.getSysUserByUserName(username);
        // 如果查询到的User为空, 则抛出.
        if (sysUser == null) throw new UsernameNotFoundException("admin: " + username + "do not exist!");

        // 权限列表.
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        if (!username.equals("root") && !username.equals("administrator")) {
            List<String> roleNames = roleMapper.listRoleNameFromUserAndUserGroupByUsername(username);
            if (roleNames.size() != 0) {
                List<SysApiDO> sysApiDOList = apiMapper.listSysApiByRoleName(roleNames);
                // 这里看看能不能写得更简洁? 比如,使用流替换掉该方法?
                sysApiDOList.forEach((sysApiDO) -> {
                    GrantedAuthority grantedAuthority = new MyGrantedAuthorityDTO(sysApiDO.getMethod(), sysApiDO.getApiPath(),sysApiDO.getMethod() + ";" + sysApiDO.getApiPath());
                    grantedAuthorities.add(grantedAuthority);
                });
            }
        } else {
            GrantedAuthority grantedAuthority = new MyGrantedAuthorityDTO("ALL", "/**","ALL;/**");
            grantedAuthorities.add(grantedAuthority);
        }

        return new SysUserDetails(sysUser, grantedAuthorities);
    }

    static final class SysUserDetails extends SysUserDO implements UserDetails {

        // 权限集
        private final Set<GrantedAuthority> authorities;

        public SysUserDetails(SysUserDO sysUserDO, Collection<? extends GrantedAuthority> authorities) {
            super(sysUserDO);
            Assert.isTrue(sysUserDO.getUsername() != null
                            && !"".equals(sysUserDO.getUsername())
                            && sysUserDO.getPassword() != null,
                    "Cannot pass null or empty values to constructor");
            this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
        }

        /**
         * Returns the authorities granted to the user. Cannot return <code>null</code>.
         *
         * @return the authorities, sorted by natural key (never <code>null</code>)
         */
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return this.authorities;
        }

        /**
         * Indicates whether the user's account has expired. An expired account cannot be
         * authenticated.
         *
         * @return <code>true</code> if the user's account is valid (ie non-expired),
         * <code>false</code> if no longer valid (ie expired)
         */
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }

        /**
         * Indicates whether the user is locked or unlocked. A locked user cannot be
         * authenticated.
         *
         * @return <code>true</code> if the user is not locked, <code>false</code> otherwise
         */
        @Override
        public boolean isAccountNonLocked() {
            return this.getAccountNonLocked();
        }

        /**
         * Indicates whether the user's credentials (password) has expired. Expired
         * credentials prevent authentication.
         *
         * @return <code>true</code> if the user's credentials are valid (ie non-expired),
         * <code>false</code> if no longer valid (ie expired)
         */
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }

        /**
         * Indicates whether the user is enabled or disabled. A disabled user cannot be
         * authenticated.
         *
         * @return <code>true</code> if the user is enabled, <code>false</code> otherwise
         */
        @Override
        public boolean isEnabled() {
            return this.getEnabled();
        }

        /**
         * 把权限集合转成SortedSet格式.
         *
         * @param authorities 权限集合.
         * @return SortedSet 格式的权限集合.
         */
        private static SortedSet<GrantedAuthority> sortAuthorities(Collection<? extends GrantedAuthority> authorities) {
            Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");
            // Ensure array iteration order is predictable (as per
            // UserDetails.getAuthorities() contract and SEC-717)
            SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet<>(new SysUserDetails.AuthorityComparator());
            for (GrantedAuthority grantedAuthority : authorities) {
                Assert.notNull(grantedAuthority, "GrantedAuthority list cannot contain any null elements");
                sortedAuthorities.add(grantedAuthority);
            }
            return sortedAuthorities;
        }

        private static class AuthorityComparator implements Comparator<GrantedAuthority>, Serializable {
            @Serial
            private static final long serialVersionUID = 570L;

            private AuthorityComparator() {
            }

            public int compare(GrantedAuthority g1, GrantedAuthority g2) {
                if (g2.getAuthority() == null) {
                    return -1;
                } else {
                    return g1.getAuthority() == null ? 1 : g1.getAuthority().compareTo(g2.getAuthority());
                }
            }
        }
    }
}
